#include "StdAfx.h"
#include "System1Machine.h"

static dip_t system1dip[] =
	{
	{"Unknown",1,1,{{"on",0},{"off",1}}},
	{"Unknown",1,2,{{"on",0},{"off",2}}},
	{"Unknown",1,4,{{"on",0},{"off",4}}},
	{"Unknown",1,8,{{"on",0},{"off",8}}},
	{"Unknown",1,0x10,{{"on",0},{"off",0x10}}},
	{"Unknown",1,0x20,{{"on",0},{"off",0x20}}},
	{"Unknown",1,0x40,{{"on",0},{"off",0x40}}},
	{"Unknown",1,0x80,{{"on",0},{"off",0x80}}},
	{0,0,0,0,0}	
	};

#define WATCHDOG_MAINCPU	1
#define WATCHDOG_SOUNDCPU	2
#define WATCHDOG_SUBCPU		4
#define WATCHDOG_MASK		(WATCHDOG_MAINCPU | WATCHDOG_SOUNDCPU | WATCHDOG_SUBCPU)

void CSystem1Machine::WritePalette(u32 addr,u8 data)
{
u32 n;

if((addr & 0x1800) == 0x1800)
	{
	n = (addr & 0x6000) >> 2;
	n |= addr & 0x7FF;
	PaletteRAM[addr] = data;
	addr &= ~0x1800;
	Palette[n] = ConvertRGB24(PaletteRAM[addr],PaletteRAM[addr + 0x800],PaletteRAM[addr + 0x1000]);
	}
else
	{
/*
		int i, j;

		namcos1_cus116[offset & 0x0f] = data;

		for (i = 0x1800; i < 0x8000; i += 0x2000)
		{
			offset = (offset & 0x0f) | i;

			for (j = 0; j < 0x80; j++, offset += 0x10)
				namcos1_paletteram[offset] = data;
		}
*/
	}
}

void CSystem1Machine::MCUPatchWrite(u8 data)
{
//message("mcu patch write, data = $%02X, patch = $%02X\n",data,MCUPatch);
if(MCUPatch != 0xA6)
	{
	MCUPatch = data;
	SharedRAM[0] = data;
	}
}

void CSystem1Machine::WatchdogWrite(u32 n)
{
WatchdogData |= n;
//if(WatchdogData == WATCHDOG_MASK)
if((WatchdogData == WATCHDOG_MASK) || (ResetFlag == 0))
	WatchdogReset();
}

void CSystem1Machine::WatchdogReset()
{
//message("*** watchdog reset, %d cycles passed\n",8-WatchdogCounter);
WatchdogData = 0;
WatchdogCounter = 30;
}

void CSystem1Machine::WatchdogUpdate()
{
if(WatchdogCounter == 0)
	{
	message("watchdog resetting machine");
	if((WatchdogData & 1) == 0)	message(", main cpu bad");
	if((WatchdogData & 2) == 0)	message(", sound cpu bad");
	if((WatchdogData & 4) == 0)	message(", sub cpu bad");
	message("\n");
	MainCPU->Reset();
	SoundCPU->Reset();
	SubCPU->Reset();
	WatchdogReset();
	}
else
	WatchdogCounter--;
}

CSystem1Machine::CSystem1Machine()
{
CPU[0] = new CDead6809();
CPU[1] = new CDead6809();
CPU[2] = new CDead6809();
CPU[3] = new CDead63701();
MainCPU = (CDead6809*)CPU[0];
SoundCPU = (CDead6809*)CPU[1];
SubCPU = (CDead6809*)CPU[2];
MCU = (CDead63701*)CPU[3];
MainMMU = new CNamcoSystem1MMU(this,MainRAM,"main");
SubMMU = new CNamcoSystem1MMU(this,MainRAM,"sub");
}

CSystem1Machine::~CSystem1Machine()
{
delete MainMMU;
delete SubMMU;
}

int CSystem1Machine::Init()
{
MainCPU->Init();
SoundCPU->Init();
SubCPU->Init();
MCU->Init();
MainCPU->SetUser(this);
SoundCPU->SetUser(this);
SubCPU->SetUser(this);
MCU->SetUser(this);
MainCPU->SetOp(ReadMainCPU);
MainCPU->SetRead(ReadMainCPU);
MainCPU->SetWrite(WriteMainCPU);
SoundCPU->SetOp(ReadSoundCPU);
SoundCPU->SetRead(ReadSoundCPU);
SoundCPU->SetWrite(WriteSoundCPU);
SubCPU->SetOp(ReadSubCPU);
SubCPU->SetRead(ReadSubCPU);
SubCPU->SetWrite(WriteSubCPU);
MCU->SetOp(ReadMCU);
MCU->SetRead(ReadMCU);
MCU->SetWrite(WriteMCU);
RomSet->SetDip(0,system1dip);
InitNVRAM(RomSet->GetName(),NVRAM,0x800);
return(0);
}

void CSystem1Machine::Kill()
{
}

void CSystem1Machine::Reset()
{
int i,n;
u8 b;
CRom *Rom;

for(n=RomSet->Num(),i=0;i<n;i++)
	{
	Rom = RomSet->GetROM(i);
	if(Rom->Data == 0) continue;
	if(Rom->Flags & ROM_TEMP)	memcpy(ROM + Rom->LoadAddr,Rom->Data,Rom->Size);
	if(Rom->Flags & ROM_CPU3)	memcpy(SoundROM + Rom->LoadAddr,Rom->Data,Rom->Size);
	if(Rom->Flags & ROM_CPU4)	memcpy(MCUROM + Rom->LoadAddr,Rom->Data,Rom->Size);
	if(Rom->Flags & ROM_VROM1)	memcpy(CharMaskROM + Rom->LoadAddr,Rom->Data,Rom->Size);
	if(Rom->Flags & ROM_VROM2)	memcpy(CharROM + Rom->LoadAddr,Rom->Data,Rom->Size);
	if(Rom->Flags & ROM_VROM3)	memcpy(SpriteROM + Rom->LoadAddr,Rom->Data,Rom->Size);
	}
//bit 16 of address bus is backwards
for(i=0x380000;i<0x400000;i++)
	{
	if((i & 0x10000) == 0)
		{
		b = ROM[i];
		ROM[i] = ROM[i + 0x10000];
		ROM[i + 0x10000] = b;
		}
	}
//clear rams
for(i=0;i<0x8000;i++)
	{
	MainRAM[i] = 0;
	SubRAM[i] = 0;
	SoundRAM[i & 0x1FFF] = 0;
	SharedRAM[i & 0x7FF] = 0;
	VideoRAM[i] = 0;
	SpriteRAM[i & 0xFFF] = 0;
	PaletteRAM[i] = 0;
	PControl[i & 0x1F] = 0;
	CUS30_WaveRAM[i & 0x3FF] = 0;
	if(i < 0x1800)
		Palette[i] = 0;
	}
Poll();
//Sound->SetCallback(ProcessSound);
MainMMU->Reset();
SubMMU->Reset();
//do i need to set ram?
MainMMU->Bankswitch(0x000,0x01);
MainMMU->Bankswitch(0x001,0x80);
MainMMU->Bankswitch(0x200,0x01);
MainMMU->Bankswitch(0x201,0x80);
MainMMU->Bankswitch(0xE00,0x03);		//map in vectors and last rom bank
MainMMU->Bankswitch(0xE01,0xFF);
SubMMU->Bankswitch(0x000,0x01);
SubMMU->Bankswitch(0x001,0x80);
SubMMU->Bankswitch(0xE00,0x03);		//map in vectors and last rom bank
SubMMU->Bankswitch(0xE01,0xFF);
MainCPU->Reset();
SoundCPU->Reset();
SubCPU->Reset();
MCU->Reset();
WatchdogReset();
Scanline = 0;
ResetFlag = 0;
MCUBankPtr = MCUROM;
MCUPatch = 0;
message("namco system 1 machine reset\n");
}

void CSystem1Machine::Poll()
{
u32 n;

Input1 = 0xFF;		//player 1 input/start button
Input2 = 0xFF;		//player 2 input/start button
Input3 = 0xF8;		//coin/service
Input->Poll();
n = Input->Get();
if(n & I_RIGHT)	Input1 &= ~1;
if(n & I_LEFT)		Input1 &= ~2;
if(n & I_DOWN)		Input1 &= ~4;
if(n & I_UP)		Input1 &= ~8;
if(n & I_B1)		Input1 &= ~0x10;
if(n & I_B2)		Input1 &= ~0x20;
if(n & I_B3)		Input1 &= ~0x40;
if(n & I_START1)	Input1 &= ~0x80;
if(n & I2_RIGHT)	Input2 &= ~1;
if(n & I2_LEFT)	Input2 &= ~2;
if(n & I2_DOWN)	Input2 &= ~4;
if(n & I2_UP)		Input2 &= ~8;
if(n & I2_B1)		Input2 &= ~0x10;
if(n & I2_B2)		Input2 &= ~0x20;
if(n & I2_B3)		Input2 &= ~0x40;
if(n & I_START2)	Input2 &= ~0x80;
if(n & I_COIN2)	Input3 &= ~8;
if(n & I_COIN1)	Input3 &= ~0x10;
if(n & I_SERVICE)	Input3 &= ~0x20;
if(n & I_ADVANCE)	Input3 &= ~0x40;
}

void CSystem1Machine::Blit()
{
u8 *S;
u32 *D;
int x,y,p;

Video->StartBlit();
S = Screen;
D = (u32*)Video->GetSurface();
RenderScreen(Screen,288);
p = Video->GetPitch() / 4;
for(y=0;y<244;y++)
	{
	for(x=0;x<288;x++)
		D[x] = Palette[S[x]];
	D += p;
	S += 288;
	}
Video->StopBlit();
}

#define XTAL 49152000
#define SCANLINES 272
#define FPS 60
#define LC(nn)	(XTAL / nn / SCANLINES / FPS)

void CSystem1Machine::Line(u32 mainc,u32 soundc,int irq)
{
MainCPU->Execute(LC(32));
SoundCPU->Execute(LC(32));
SubCPU->Execute(LC(32));
MCU->Execute(LC(32));
Scanline++;
if((Scanline & 7) == 7)
	WatchdogUpdate();
if(Scanline >= 272)
	Scanline = 0;
}

void CSystem1Machine::Frame()
{
int i;

for(i=0;i<272;i++)
	Line(0,0,0);
Blit();
}

void CSystem1Machine::SaveState(CState *s)
{
MainCPU->SaveState(s);
SoundCPU->SaveState(s);
SubCPU->SaveState(s);
}

void CSystem1Machine::LoadState(CState *s)
{
MainCPU->LoadState(s);
SoundCPU->LoadState(s);
SubCPU->LoadState(s);
}

static void drawtile_8x8_8bpp(u8 *dest,int p,int tilenum,u8 x,u8 y,u8 *b[])
{
u8 *s = dest + x + (y * p);
u32 adder = tilenum * 8;
int i,j;
u8 b0,b1,b2;
u8 *bb[3];

bb[0] = b[0] + adder;
bb[1] = b[1] + adder;
bb[2] = b[2] + adder;
for(j=0;j<8;j++)
	{
	b0 = *bb[0]++;
	b1 = *bb[1]++;
	b2 = *bb[2]++;
	for(i=8-1;i>=0;i--)
		{
		u8 color = (u8)(((b0 >> i) & 1) | (((b1 >> i) & 1) << 1) | (((b2 >> i) & 1) << 2));

		if(color)
			*s++ = color;
		else
			s++;
		}
	s += p - 8;
	}
}

/*
tiles are 8bpp each, each byte represents a pixel
*/
void CSystem1Machine::RenderScreen(u8 *dest,int pitch)
{
/*
  video ram map
  0000-1fff : scroll playfield (0) : 64*64*2
  2000-3fff : scroll playfield (1) : 64*64*2
  4000-5fff : scroll playfield (2) : 64*64*2
  6000-6fff : scroll playfield (3) : 64*32*2
  7000-700f : ?
  7010-77ef : fixed playfield (4)  : 36*28*2
  77f0-77ff : ?
  7800-780f : ?
  7810-7fef : fixed playfield (5)  : 36*28*2
  7ff0-7fff : ?
*/
#define PF0	(VideoRAM + 0x0000)	//$0000-$1FFF
#define PF1	(VideoRAM + 0x2000)	//$2000-$1FFF
#define PF2	(VideoRAM + 0x4000)	//$4000-$1FFF
#define PF3 (VideoRAM + 0x6000)	//$6000-$1FFF
#define PF4	(VideoRAM + 0x7010)	//$7010-$77EF
#define PF5	(VideoRAM + 0x7810)	//$7810-$7FEF

#define CalcCharIndex(dd,ii,nn)	\
	ii *= 2;								\
	dd = nn[ii + 1] | ((nn[ii] & 0x3F) << 8);

int x,y,i;
u32 n;

//w=288 h=244
for(y=0;y<(244/8);y++)
	{
	for(x=0;x<(288/8);x++)
		{
		i = x * (y * 64);
		CalcCharIndex(n,i,PF0);
		//drawtile_8x8_8bpp(
		}
	}
}

u8 CSystem1Machine::ReadMainCPU(void *user,u32 addr)
{
CSystem1Machine *M = (CSystem1Machine*)user;

return(M->MainMMU->Read(user,addr));
}

void CSystem1Machine::WriteMainCPU(void *user,u32 addr,u8 data)
{
CSystem1Machine *M = (CSystem1Machine*)user;

//message("main cpu write, $%04X = $%02X\n",addr,data);
if(addr < 0xE000)						//banked write
	{
	M->MainMMU->Write(user,addr,data);
	return;
	}
if(addr < 0xF000)					//bankswitch write
	{
	M->MainMMU->Bankswitch(addr,data);
	return;
	}
switch(addr)
	{
	case 0xF000:				//cpu control register
		message("** cpu control = $%02X (pc = $%04x)\n",data,M->MainCPU->GetPC());
		if((data & 1) ^ M->ResetFlag)
			{
			M->MCUPatch = 0;
			M->ResetFlag = data & 1;
			message("reset flag set\n");
			}
		if((data & 1) == 0)	//reset
		//if(M->ResetFlag)	//reset
			{
			message("resetting\n");
			M->SoundCPU->Reset();
			M->SubCPU->Reset();
			M->MCU->Reset();
			M->WatchdogReset();
			}
		return;
	case 0xF200:				//watchdog write
		M->WatchdogWrite(WATCHDOG_MAINCPU);
		return;
	case 0xF600:	//irq ack
		break;
	case 0xF800:	//firq ack
		break;
	case 0xFA00:
		M->SubCPU->FIRQ();
		break;
	case 0xFC00:
	case 0xFC01:
		message("sub mmu bankswitch write, addr = $%04X, data = $%02X\n",addr,data);
		M->SubMMU->Bankswitch(0xE00,3);
		M->SubMMU->Bankswitch(0xE01,data);
		return;
	}
message("unhandled write (main cpu, addr $%04X = $%02X)\n",addr,data);
}

u8 CSystem1Machine::ReadSoundCPU(void *user,u32 addr)
{
CSystem1Machine *M = (CSystem1Machine*)user;

if(addr >= 0xC000)				//fixed rom
	return(M->SoundROM[addr]);
if(addr < 0x4000)					//bankswitched roms
	return(M->SoundROMPtr[addr]);
switch(addr & 0xF000)
	{
	case 0x5000:
		if(addr < 0x5400)
			return(M->CUS30_WaveRAM[addr & 0x3FF]);
		break;
	case 0x7000:
		if(addr < 0x7400)
			return(M->SharedRAM[addr & 0x7FF]);
		break;
	case 0x8000:
	case 0x9000:
		return(M->SoundRAM[addr & 0x1FFF]);
	}
message("unhandled read (sound cpu, addr $%04X)\n",addr);
return(0);
}

void CSystem1Machine::WriteSoundCPU(void *user,u32 addr,u8 data)
{
CSystem1Machine *M = (CSystem1Machine*)user;

switch(addr & 0xF000)
	{
	case 0x5000:
		break;
	case 0x7000:
		if(addr < 0x7400)
			{
			if(addr == 0x7000)
				{
				M->MCUPatchWrite(data);
				return;
				}
			M->SharedRAM[addr & 0x3FF] = data;
			return;
			}
		break;
	case 0x8000:
	case 0x9000:
		M->SoundRAM[addr & 0x1FFF] = data;
		return;
	case 0xC000:
		if(addr <= 0xC001)
			M->SoundROMPtr = M->SoundROM + 0x10000 + ((data >> 4) & 7) * 0x4000;
		break;
	case 0xD000:
		if(addr == 0xD001)
			{
			M->WatchdogWrite(WATCHDOG_SOUNDCPU);
			return;
			}
		break;
	}
message("unhandled write (sound cpu, addr $%04X = $%02X)\n",addr,data);
}

u8 CSystem1Machine::ReadSubCPU(void *user,u32 addr)
{
CSystem1Machine *M = (CSystem1Machine*)user;

return(M->SubMMU->Read(user,addr));
}

void CSystem1Machine::WriteSubCPU(void *user,u32 addr,u8 data)
{
CSystem1Machine *M = (CSystem1Machine*)user;

if(addr < 0xE000)
	{
	M->SubMMU->Write(user,addr,data);
	return;
	}
if(addr < 0xF000)					//bankswitch write
	{
	M->SubMMU->Bankswitch(addr,data);
	return;
	}
switch(addr & 0xFE00)
	{
	case 0xF200:				//watchdog write
		M->WatchdogWrite(WATCHDOG_SUBCPU);
		return;
	case 0xF600:
		break;
	case 0xF800:
		break;
	}
message("unhandled write (sub cpu, addr $%04X = $%02X)\n",addr,data);
}

u8 CSystem1Machine::ReadMCU(void *user,u32 addr)
{
CSystem1Machine *M = (CSystem1Machine*)user;

if(addr >= 0xF000)
	return(M->MCUROM[addr]);
if(addr >= 0x4000 && addr < 0xC000)
	return(M->MCUBankPtr[addr - 0x4000]);
if(addr >= 0xC000 && addr < 0xC800)
	return(M->SharedRAM[addr & 0x3FF]);
if(addr >= 0xC800 && addr < 0xD000)
	return(M->NVRAM[addr & 0x7FF]);
if(addr < 0x100)
	{
	if(addr >= 0x80)
		return(M->MCURAM[addr & 0x7F]);
	}
message("mcu read $%04X\n",addr);
return(0);
}

void CSystem1Machine::WriteMCU(void *user,u32 addr,u8 data)
{
CSystem1Machine *M = (CSystem1Machine*)user;
u32 BankAddr;

if(addr <= 0x101)
	{
	if(addr >= 0x80)
		{
		M->MCURAM[addr & 0x7F] = data;
		return;
		}
	else if(addr == 0x100)		//63701 port1
		{
		
		}
	else if(addr == 0x101)		//63701 port2
		{
		
		}
	else if(addr < 0x20)
		return;
	}
if(addr >= 0xC000 && addr < 0xC800)
	{
	if(addr == 0xC000)
		{
		M->MCUPatchWrite(data);
		return;
		}
	M->SharedRAM[addr & 0x3FF] = data;
	return;
	}
if(addr >= 0xC800 && addr < 0xD000)
	{
	M->NVRAM[addr & 0x7FF] = data;
	return;
	}
switch(addr)
	{
	case 0xD800:		//rom bankswitch
		//message("mcu bankswitch write $%04X = $%02X\n",addr,data);
		switch(data & 0xFC)
			{
			default:
				message("mcu: bad bankswitch value $%02X\n",data);
			case 0xF8:
				BankAddr = 0x10000;
				data ^= 2;
				break;
			case 0xF4:
				BankAddr = 0x30000;
				break;
			case 0xEC:
				BankAddr = 0x50000;
				break;
			case 0xDC:
				BankAddr = 0x70000;
				break;
			case 0xBC:
				BankAddr = 0x90000;
				break;
			case 0x7C:
				BankAddr = 0xB0000;
				break;
			}
		M->MCUBankPtr = M->MCUROM + BankAddr + ((data & 3) * 0x8000);
		return;
	}
message("mcu write $%04X = $%02X\n",addr,data);
}
